package simpledb.tx.recovery;

import static simpledb.tx.recovery.LogRecord.*;
import simpledb.file.Block;
import simpledb.buffer.Buffer;
import simpledb.server.SimpleDB;
import java.util.*;

/**
 * The recovery manager. Each transaction has its own recovery manager.<br>
 * 恢复管理器，没个事务都有自己的恢复管理器
 * 
 * @author Edward Sciore
 */
public class RecoveryMgr {
  private int txnum;

  /**
   * Creates a recovery manager for the specified transaction.<br>
   * 为指定的事务创建恢复管理器
   * 
   * @param txnum the ID of the specified transaction
   */
  public RecoveryMgr(int txnum) {
    this.txnum = txnum;
    new StartRecord(txnum).writeToLog();
  }

  /**
   * Writes a commit record to the log, and flushes it to disk.<br>
   * 将提交的记录写入日志并将其刷新到磁盘
   */
  public void commit() {
    SimpleDB.bufferMgr().flushAll(txnum);
    int lsn = new CommitRecord(txnum).writeToLog();
    SimpleDB.logMgr().flush(lsn);
  }

  /**
   * Writes a rollback record to the log, and flushes it to disk.
   * 将回滚记录写入到日志，并刷新到磁盘
   */
  public void rollback() {
    doRollback();
    SimpleDB.bufferMgr().flushAll(txnum);
    int lsn = new RollbackRecord(txnum).writeToLog();
    SimpleDB.logMgr().flush(lsn);
  }

  /**
   * Recovers uncompleted transactions from the log, then writes a quiescent
   * checkpoint record to the log and flushes it. <br>
   * 从日志中恢复未完成的事务，然后把静态检查点日志记录写入到日志并将其刷新.
   */
  public void recover() {
    doRecover();
    SimpleDB.bufferMgr().flushAll(txnum);
    int lsn = new CheckpointRecord().writeToLog();
    SimpleDB.logMgr().flush(lsn);

  }

  /**
   * Writes a setint record to the log, and returns its lsn. Updates to
   * temporary files are not logged; instead, a "dummy" negative lsn is
   * returned.<br>
   * 将setint记录写入到日志，并返回其lsn。不会记录临时文件的更新，返回"dummy"负lsn。
   * 
   * @param buff the buffer containing the page 包含页面的缓冲区
   * @param offset the offset of the value in the page 值在页面的偏移量
   * @param newval the value to be written 要写入的值
   */
  public int setInt(Buffer buff, int offset, int newval) {
    int oldval = buff.getInt(offset);
    Block blk = buff.block();
    if (isTempBlock(blk))
      return -1;
    else
      return new SetIntRecord(txnum, blk, offset, oldval).writeToLog();
  }

  /**
   * Writes a setstring record to the log, and returns its lsn. Updates to
   * temporary files are not logged; instead, a "dummy" negative lsn is
   * returned.<br>
   * 将setstring记录写入到日志，并返回其lsn。不会记录临时文件的更新，返回"dummy"负lsn。
   * 
   * @param buff the buffer containing the page
   * @param offset the offset of the value in the page
   * @param newval the value to be written
   */
  public int setString(Buffer buff, int offset, String newval) {
    String oldval = buff.getString(offset);
    Block blk = buff.block();
    if (isTempBlock(blk))
      return -1;
    else
      return new SetStringRecord(txnum, blk, offset, oldval).writeToLog();
  }

  /**
   * Rolls back the transaction. The method iterates through the log records,
   * calling undo() for each log record it finds for the transaction, until it
   * finds the transaction's START record. <br>
   * 回滚事务，这个方法通过迭代日志记录,通过事务为找到的每个日志记录调用undo()方法，直到它查找到的事务的记录是START类型。
   */
  private void doRollback() {
    Iterator<LogRecord> iter = new LogRecordIterator();
    while (iter.hasNext()) {
      LogRecord rec = iter.next();
      if (rec.txNumber() == txnum) {
        if (rec.op() == START)
          return;
        rec.undo(txnum);
      }
    }
  }

  /**
   * Does a complete database recovery. The method iterates through the log
   * records. Whenever it finds a log record for an unfinished transaction, it
   * calls undo() on that record. The method stops when it encounters a
   * CHECKPOINT record or the end of the log.<br>
   * 完成数据库恢复，这个方法通过遍历日志记录，每当它找到未完成的事务的日志记录时，它会调用记录的undo()方法，
   * 这个方法在遇到CHECKPOINT记录和到到达日志结尾是停止.
   */
  private void doRecover() {
    Collection<Integer> finishedTxs = new ArrayList<Integer>();
    Iterator<LogRecord> iter = new LogRecordIterator();
    while (iter.hasNext()) {
      LogRecord rec = iter.next();
      if (rec.op() == CHECKPOINT)
        return;
      if (rec.op() == COMMIT || rec.op() == ROLLBACK)
        finishedTxs.add(rec.txNumber());
      else if (!finishedTxs.contains(rec.txNumber()))
        rec.undo(txnum);
    }
  }

  /**
   * Determines whether a block comes from a temporary file or not. <br>
   * 确定块是否来自于临时文件
   */
  private boolean isTempBlock(Block blk) {
    return blk.fileName().startsWith("temp");
  }
}
